package linwen.wang.application.impl.v3;

import linwen.wang.application.OrderDetailService;
import linwen.wang.domain.AddressConvertMapper;
import linwen.wang.domain.OrderConvertMapper;
import linwen.wang.domain.ProductConvertMapper;
import linwen.wang.domain.UserConvertMapper;
import linwen.wang.entity.Address;
import linwen.wang.entity.Order;
import linwen.wang.entity.Product;
import linwen.wang.entity.User;
import linwen.wang.service.IAddressService;
import linwen.wang.service.IOrderService;
import linwen.wang.service.IProductService;
import linwen.wang.service.IUserService;
import linwen.wang.vo.AddressVO;
import linwen.wang.vo.OrderDetailVO;
import linwen.wang.vo.ProductVO;
import linwen.wang.vo.UserVO;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

@Service
public class OrderDetailServiceV3 implements OrderDetailService {

    private ExecutorService executorService;

    @Autowired
    private IUserService userService;

    @Autowired
    private IProductService productService;

    @Autowired
    private IOrderService orderService;

    @Autowired
    private IAddressService addressService;

    @Autowired
    private OrderConvertMapper orderConvertMapper;

    @Autowired
    private AddressConvertMapper addressConvertMapper;

    @Autowired
    private UserConvertMapper userConvertMapper;

    @Autowired
    private ProductConvertMapper productConvertMapper;

    @PostConstruct
    public void init(){
        this.executorService = Executors.newFixedThreadPool(20);
    }


    @SneakyThrows
    @Override
    public List<? extends OrderDetailVO> getByUserId(Long userId) {
        List<Order> orders = this.orderService.getByUserId(userId);

        List<OrderDetailVOV3> orderDetailVOS = orders.stream()
                .map(order -> new OrderDetailVOV3(this.orderConvertMapper.orderConvertOrderVO(order)))
                .collect(toList());

        List<Callable<Void>> callables = new ArrayList<>(3);
        callables.add(() -> {
            bindUser(orders, orderDetailVOS);
            return null;
        });

        callables.add(() ->{
            bindAddress(orders, orderDetailVOS);
            return null;
        });

        callables.add(() -> {
            bindProduct(orders, orderDetailVOS);
            return null;
        });
        this.executorService.invokeAll(callables);
        return orderDetailVOS;
    }

    private void bindProduct(List<Order> orders, List<OrderDetailVOV3> orderDetailVOS) {
        List<Long> productIds = orders.stream()
                .map(Order::getProductId)
                .collect(toList());
        List<Product> products = this.productService.listByIds(productIds);
        Map<Long, Product> productMap = products.stream()
                .collect(toMap(Product::getId, Function.identity(), (a, b) -> a));
        for (OrderDetailVOV3 orderDetailVO : orderDetailVOS) {
            Product product = productMap.get(orderDetailVO.getOrder().getProductId());
            ProductVO productVO = productConvertMapper.productConvertProductVO(product);
            orderDetailVO.setProduct(productVO);
        }
    }

    private void bindAddress(List<Order> orders, List<OrderDetailVOV3> orderDetailVOS) {
        List<Long> addressIds = orders.stream()
                .map(Order::getAddressId)
                .collect(toList());
        List<Address> addresses = this.addressService.listByIds(addressIds);
        Map<Long, Address> addressMap = addresses.stream()
                .collect(toMap(Address::getId, Function.identity(), (a, b) -> a));
        for (OrderDetailVOV3 orderDetailVO : orderDetailVOS){
            Address address = addressMap.get(orderDetailVO.getOrder().getAddressId());
            AddressVO addressVO = addressConvertMapper.addressConvertAddressVO(address);
            orderDetailVO.setAddress(addressVO);
        }
    }

    private void bindUser(List<Order> orders, List<OrderDetailVOV3> orderDetailVOS) {
        List<Long> userIds = orders.stream()
                .map(Order::getUserId)
                .collect(toList());
        List<User> users = this.userService.listByIds(userIds);
        Map<Long, User> userMap = users.stream()
                .collect(toMap(User::getId, Function.identity(), (a, b) -> a));
        for (OrderDetailVOV3 orderDetailVO : orderDetailVOS){
            User user = userMap.get(orderDetailVO.getOrder().getUserId());
            UserVO userVO = userConvertMapper.userConvertUserVO(user);
            orderDetailVO.setUser(userVO);
        }
    }
}